﻿using Bookvar.ApplicationModel;
using Bookvar.Common;
using Bookvar.ObjectModel;
using Bookvar.ServiceModel;
using Bookvar.UI.Arrangers;
using Bookvar.UI.ViewModel;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;

namespace Bookvar.UI
{
	[TemplatePart( Name = TopicNameTemplateName, Type = typeof ( TextBox ) )]
	[TemplatePart( Name = ChildrenToggleTemplateName, Type = typeof ( ToggleButton ) )]
	[TemplatePart( Name = ResourcesTemplateName, Type = typeof ( ResourceItemsControl ) )]
	public class TopicContentPresenter : ContentPresenter
	{
		private const string ChildrenToggleTemplateName = "PART_ChildrenToggle";
		private const string ResourcesTemplateName = "PART_Resources";
		private const string TopicNameTemplateName = "PART_TopicName";
		private readonly MindMapPanel owner;
		private readonly TopicContentPresenter parentPresenter;
		private readonly TopicViewModel viewModel;

		private Dictionary<string, IAdorner> adorners;
		private IAdorner attachmentAdorner;
		private AttachmentButton attachmentButton;
		private List<TopicContentPresenter> children;
		private ToggleButton childrenToggleButton;
		private IAdorner dragAdorner;
		private IAdorner hyperlinkAdorner;
		private HyperlinkButton hyperlinkButton;

		private TextBox nameTextBox;
		private IAdorner notesAdorner;
		private NotesButton notesButton;

		private ResourceItemsControl resourceItemsControl;
		private bool topicsInitialized;

		#region Properties

		public Dictionary<string, IAdorner> Adorners
		{
			get
			{
				if ( adorners == null )
				{
					adorners = new Dictionary<string, IAdorner>();
				}
				return adorners;
			}
		}

		public IList<TopicContentPresenter> Children
		{
			get
			{
				if ( children == null )
				{
					children = new List<TopicContentPresenter>();
				}
				return children;
			}
		}

		public Topic Topic
		{
			get { return Content as Topic; }
		}

		public TopicContentPresenter ParentTopicPresenter
		{
			get { return parentPresenter; }
		}

		public bool HasParent
		{
			get { return parentPresenter != null; }
		}

		public MindMapPanel Owner
		{
			get { return owner; }
		}

		internal TextBox NameTextBox
		{
			get { return nameTextBox; }
		}

		internal ResourceItemsControl ResourceItemsControl
		{
			get { return resourceItemsControl; }
		}

		internal ToggleButton ChildrenToggleButton
		{
			get { return childrenToggleButton; }
		}

		public AttachmentButton AttachmentButton
		{
			get { return attachmentButton; }
			protected set { attachmentButton = value; }
		}

		private static IAdornerService AdornerService
		{
			get { return BookvarApplication.Current.ServiceLayer.GetService<IAdornerService>(); }
		}

		#region X

		[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "X" )]
		public double X
		{
			get
			{
				double x = Canvas.GetLeft( this );
				return double.IsNaN( x ) ? 0 : x;
			}
			set { Canvas.SetLeft( this, value ); }
		}

		#endregion

		#region Y

		[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Y" )]
		public double Y
		{
			get
			{
				double y = Canvas.GetTop( this );
				return double.IsNaN( y ) ? 0 : y;
			}
			set { Canvas.SetTop( this, value ); }
		}

		#endregion

		#region AreChildrenVisible

		public static readonly DependencyProperty AreChildrenVisibleProperty;
		private static readonly DependencyPropertyKey AreChildrenVisiblePropertyKey;

		public bool AreChildrenVisible
		{
			get
			{
				if ( childrenToggleButton != null )
				{
					return childrenToggleButton.IsChecked == false;
				}
				return true;
			}
		}

		#endregion

		#region HasChildren

		public static readonly DependencyProperty HasChildrenProperty;
		private static readonly DependencyPropertyKey HasChildrenPropertyKey;

		public bool HasChildren
		{
			get { return (bool) GetValue( HasChildrenProperty ); }
			private set { SetValue( HasChildrenPropertyKey, value ); }
		}

		#endregion

		#region IsMoved

		public static readonly DependencyProperty IsMovedProperty =
			DependencyProperty.Register( "IsMoved", typeof ( bool ), typeof ( TopicContentPresenter ),
			                             new FrameworkPropertyMetadata( BooleanBoxes.FalseBox ) );

		public bool IsMoved
		{
			get { return (bool) GetValue( IsMovedProperty ); }
			set { SetValue( IsMovedProperty, value ); }
		}

		#endregion

		#region GrowingDirection

		private static readonly DependencyProperty GrowingDirectionProperty =
			DependencyProperty.Register(
				"GrowingDirection", typeof ( GrowingDirection ), typeof ( TopicContentPresenter ),
				new FrameworkPropertyMetadata( GrowingDirection.Unset, OnGrowingDirectionChanged, CoerceGrowingDirection ) );

		public GrowingDirection GrowingDirection
		{
			get
			{
				CoerceValue( GrowingDirectionProperty );
				return (GrowingDirection) GetValue( GrowingDirectionProperty );
			}
			set { SetValue( GrowingDirectionProperty, value ); }
		}

		private static void OnGrowingDirectionChanged( DependencyObject sender, DependencyPropertyChangedEventArgs args )
		{
		}

		private static object CoerceGrowingDirection( DependencyObject sender, object value )
		{
			GrowingDirection direction = (GrowingDirection) value;
			if ( direction == GrowingDirection.Unset )
			{
				var presenter = (TopicContentPresenter) sender;
				if ( presenter.parentPresenter != null &&
				     presenter.parentPresenter.GrowingDirection != GrowingDirection.Unset )
				{
					return presenter.parentPresenter.GrowingDirection;
				}

				if ( !presenter.Topic.IsMainTopic )
				{
					if ( presenter.X <= presenter.owner.MainTopicPresenter.X )
					{
						return GrowingDirection.Left;
					}

					return GrowingDirection.Right;
				}
			}

			return value;
		}

		#endregion

		public void AddAdorner( string adornerName, UIElement adorningElement,
		                        AdornerPosition position, AdornerPlacement placement )
		{
			if ( Adorners.ContainsKey( adornerName ) )
			{
				return;
			}
			IAdorner adorener = AdornerService.AdornTopic( Topic, adorningElement, position, placement );
			Adorners.Add( adornerName, adorener );
		}

		public void RemoveAdorner( string adornerName )
		{
			if ( Adorners.ContainsKey( adornerName ) )
			{
				IAdorner adorner = Adorners[ adornerName ];
				AdornerService.RemoveAdorner( adorner );
				Adorners.Remove( adornerName );
			}
		}

		#endregion

		[SuppressMessage( "Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline" )]
		static TopicContentPresenter()
		{
			FocusableProperty.OverrideMetadata( typeof ( TopicContentPresenter ), new FrameworkPropertyMetadata( true ) );

			AreChildrenVisiblePropertyKey = DependencyProperty.RegisterReadOnly("AreChildrenVisible", 
				typeof(bool), typeof(TopicContentPresenter), new UIPropertyMetadata(true));

			AreChildrenVisibleProperty = AreChildrenVisiblePropertyKey.DependencyProperty;

			HasChildrenPropertyKey = DependencyProperty.RegisterReadOnly( "HasChildren", 
				typeof ( bool ), typeof ( TopicContentPresenter ), new UIPropertyMetadata( false ) );
			HasChildrenProperty = HasChildrenPropertyKey.DependencyProperty;
		}

		public TopicContentPresenter( Topic topic, MindMapPanel owner )
		{
//            if ( topic is MainTopic )
//            {
//                GrowingDirection = GrowingDirection.Right;
//            }

			this.owner = owner;

			viewModel = new TopicViewModel( topic );
			ViewModelBase.AddCommandAndInputBindings( this, viewModel );

			SetBinding( ContentProperty, new Binding( "Topic" ) { Source = viewModel } );

			SetBinding( ContentTemplateProperty, owner.TopicTemplateBinding );
			SetBinding( ContentTemplateSelectorProperty, owner.TopicTemplateSelectorBinding );

			InitializeTopics();

			//TODO: this should be in the view model
			AttachEventHandlers();
		}

		public TopicContentPresenter( Topic topic, TopicContentPresenter parentPresenter )
			: this( topic, parentPresenter.owner )
		{
			this.parentPresenter = parentPresenter;
		}

		private void InitializeTopics()
		{
			if ( !topicsInitialized )
			{
				LoadResouces();
				foreach ( Topic topic in Topic.SubTopics )
				{
					CreateSubtopicPresenter( topic );
				}

				HasChildren = Children.Count > 0;

				topicsInitialized = true;
				//TODO: Understand why the next line was commented
				LoadAdornersRecursive();
			}
		}

		internal void LoadAdorners()
		{
			ApplyNotesAdorner();
			ApplyHyperlinkAdorner();
			ApplyAttachmentsAdorner();
		}

		private void LoadAdornersRecursive()
		{
			LoadAdorners();

			if ( Children.Count > 0 )
			{
				foreach ( TopicContentPresenter presenter in Children )
				{
					presenter.LoadAdornersRecursive();
				}
			}
		}

		//TODO: this should be done in the persister (using actions)
		private void LoadResouces()
		{
			foreach ( Resource resource in Topic.Resources )
			{
				IPersistenceService service = BookvarApplication.Current.ServiceLayer.GetService<IPersistenceService>();

				if ( resource.ResourceType == ResourceType.Video )
				{
					string tempFile = Path.GetTempFileName();

					//change the extension to match the resource ones (problem occured with MediaElement source)
					tempFile = Path.ChangeExtension( tempFile, Path.GetExtension( resource.ResourcePath ) );
					using ( FileStream fileStream = new FileStream( tempFile, FileMode.Create, FileAccess.ReadWrite ) )
					{
						Stream video =
							service.LoadFileResourceFromPersistence( owner.Mindmap.Key, resource );
						StreamUtility.CopyStream( video, fileStream );
					}

					resource.ItemSource = new Uri( tempFile, UriKind.Absolute );
				}
				else
				{
					ImageSourceConverter source = new ImageSourceConverter();
					Stream resourceStream = service.LoadFileResourceFromPersistence( owner.Mindmap.Key, resource );
					if ( resourceStream != null )
					{
						resource.ItemSource = source.ConvertFrom( resourceStream );
					}
				}
			}
		}

		protected TopicContentPresenter CreateSubtopicPresenter( Topic topic )
		{
			return CreateSubtopicPresenter( Children.Count, topic );
		}

		protected TopicContentPresenter CreateSubtopicPresenter( int index, Topic topic )
		{
			TopicContentPresenter child = new TopicContentPresenter( topic, this );

			owner.AddChild( child );
			Children.Insert( index, child );

			return child;
		}

		protected TopicContentPresenter DeleteSubtopicPresenter( Topic topic )
		{
			TopicContentPresenter child = FindChildPresenterByTopic( topic );
			if ( child != null )
			{
				Children.Remove( child );
				owner.RemoveChild( child );

				child.DetachEventHandlers();

				while ( child.Children.Count > 0 )
				{
					child.DeleteSubtopicPresenter( child.Children[ 0 ].Topic );
				}
			}
			return child;
		}

		public TopicContentPresenter FindChildPresenterByTopic( Topic topic )
		{
			foreach ( TopicContentPresenter child in Children )
			{
				if ( child.Topic.ID == topic.ID )
				{
					return child;
				}
			}
			return null;
		}

		public void Delete()
		{
			if ( parentPresenter != null )
			{
				parentPresenter.DeleteSubtopicPresenter( Topic );
			}
			else
			{
				DetachEventHandlers();
				owner.RemoveChild( this );
				while ( Children.Count > 0 )
				{
					DeleteSubtopicPresenter( Children[ 0 ].Topic );
				}
			}
		}

		protected void AttachEventHandlers()
		{
			Topic.SubTopics.CollectionChanged += OnSubtopicsChanged;
			Topic.Attachments.CollectionChanged += OnAttachmentsChanged;
			Topic.PropertyChanged += OnTopicPropertyChanged;
		}

		protected void DetachEventHandlers()
		{
			Topic.SubTopics.CollectionChanged -= OnSubtopicsChanged;
			Topic.Attachments.CollectionChanged -= OnAttachmentsChanged;
			Topic.PropertyChanged -= OnTopicPropertyChanged;
		}

		private void OnTopicPropertyChanged( object sender, PropertyChangedEventArgs e )
		{
			if ( e.PropertyName == "Note" )
			{
				ApplyNotesAdorner();
			}
			else if ( e.PropertyName == "Hyperlink" )
			{
				ApplyHyperlinkAdorner();
			}
		}

		private void ApplyNotesAdorner()
		{
			if ( Topic.Note != null )
			{
				if ( notesButton == null )
				{
					notesButton = new NotesButton();
					notesButton.Topic = Topic;
				}
				if ( AdornerService != null )
				{
					if ( notesAdorner == null )
					{
						notesAdorner = AdornerService.AdornTopic(
							Topic, notesButton, AdornerPosition.TopRight, AdornerPlacement.OutHorizontally );
					}
					else if ( !notesAdorner.IsVisible )
					{
						AdornerService.AddAdorner( notesAdorner );
					}
				}
			}
			else
			{
				if ( notesAdorner != null )
				{
					if ( AdornerService != null )
					{
						AdornerService.RemoveAdorner( notesAdorner );
						notesAdorner = null;
						notesButton = null;
					}
				}
			}
		}

		private void ApplyHyperlinkAdorner()
		{
			if ( Topic.Hyperlink != null )
			{
				if ( hyperlinkButton == null )
				{
					hyperlinkButton = new HyperlinkButton();
					hyperlinkButton.Topic = Topic;
				}
				if ( AdornerService != null )
				{
					if ( hyperlinkAdorner == null )
					{
						hyperlinkAdorner = AdornerService.AdornTopic(
							Topic, hyperlinkButton, AdornerPosition.TopRight, AdornerPlacement.OutVertically );
					}
					else if ( !hyperlinkAdorner.IsVisible )
					{
						AdornerService.AddAdorner( hyperlinkAdorner );
					}
				}
			}
			else
			{
				if ( hyperlinkAdorner != null )
				{
					if ( AdornerService != null )
					{
						AdornerService.RemoveAdorner( hyperlinkAdorner );
						hyperlinkAdorner = null;
						hyperlinkButton = null;
					}
				}
			}
		}

		private void ApplyAttachmentsAdorner()
		{
			if ( Topic.Attachments.Count > 0 )
			{
				if ( attachmentButton == null )
				{
					attachmentButton = new AttachmentButton();
					attachmentButton.Topic = Topic;
				}
				if ( AdornerService != null )
				{
					if ( attachmentAdorner == null )
					{
						attachmentAdorner = AdornerService.AdornTopic(
							Topic, attachmentButton, AdornerPosition.BottomRight, AdornerPlacement.OutHorizontally );
					}
					else if ( !attachmentAdorner.IsVisible )
					{
						AdornerService.AddAdorner( attachmentAdorner );
					}
				}
			}
			else
			{
				if ( attachmentAdorner != null )
				{
					if ( AdornerService != null )
					{
						AdornerService.RemoveAdorner( attachmentAdorner );
						attachmentAdorner = null;
						attachmentButton = null;
					}
				}
			}
		}

		//expose internally the AddLogicalChild method, used by AdornerService
		internal void AddLogicalChild( UIElement child )
		{
			base.AddLogicalChild( child );
		}

		private void OnAttachmentsChanged( object sender, NotifyCollectionChangedEventArgs e )
		{
			ApplyAttachmentsAdorner();
		}

		private void OnSubtopicsChanged( object sender, NotifyCollectionChangedEventArgs e )
		{
			ExpandChildren();

			switch ( e.Action )
			{
				case NotifyCollectionChangedAction.Add :
				{
					foreach ( Topic topic in e.NewItems )
					{
						CreateSubtopicPresenter( e.NewStartingIndex, topic );
						owner.SelectedTopic = topic;
					}
					break;
				}
				case NotifyCollectionChangedAction.Remove :
				{
					foreach ( Topic topic in e.OldItems )
					{
						TopicContentPresenter deleted = DeleteSubtopicPresenter( topic );
						if ( deleted.parentPresenter != null )
						{
							owner.SelectedTopic = topic.Parent;
						}
					}
					break;
				}
				case NotifyCollectionChangedAction.Move :
				{
					break;
				}
				case NotifyCollectionChangedAction.Replace :
				{
					break;
				}
				case NotifyCollectionChangedAction.Reset :
				{
					break;
				}
				default :
				{
					break;
				}
			}

			HasChildren = Children.Count > 0;

			owner.InvalidateTopicsArrange();
		}

		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			nameTextBox = GetTemplateChild( TopicNameTemplateName ) as TextBox;

			nameTextBox.SetBinding( TextBox.TextProperty, viewModel.CreateTopicNameBinding() );
//            nameTextBox.SetBinding( IsEnabledProperty, new Binding( "IsEditingTopicName" ) { Source = this } );

			nameTextBox.PreviewDragEnter += OnNameTextBoxPreviewDrag;
			nameTextBox.PreviewDragOver += OnNameTextBoxPreviewDrag;
			nameTextBox.PreviewDragLeave += OnNameTextBoxPreviewDrag;
			nameTextBox.PreviewMouseDown += OnNameTextBoxPreviewMouseDown;
			nameTextBox.MouseEnter += OnNameTextBoxMouseEnter;
//            nameTextBox.LostFocus += OnNameTextBoxLostFocus;

			resourceItemsControl = GetTemplateChild( ResourcesTemplateName ) as ResourceItemsControl;
			if ( resourceItemsControl != null )
			{
				resourceItemsControl.SetBinding(
					ItemsControl.ItemsSourceProperty, viewModel.CreateTopicResourcesBinding() );
			}

			childrenToggleButton = GetTemplateChild( ChildrenToggleTemplateName ) as ToggleButton;
			if ( childrenToggleButton != null )
			{
				childrenToggleButton.Click += OnChildrenToggleButtonClick;

				Binding binding = new Binding( "(HasChildren)" );
				binding.Source = this;
				binding.Converter = new BooleanToVisibilityConverter();
				childrenToggleButton.SetBinding( VisibilityProperty, binding );
			}
		}

//        private void OnNameTextBoxLostFocus( object sender, RoutedEventArgs e )
//        {
//            IsEditingTopicName = false;
//        }

		private void OnNameTextBoxMouseEnter( object sender, MouseEventArgs e )
		{
			if ( !e.Handled )
			{
				if ( !nameTextBox.IsFocused )
				{
					nameTextBox.Cursor = Cursors.Arrow;
				}
			}
		}

		private void OnNameTextBoxPreviewMouseDown( object sender, MouseButtonEventArgs e )
		{
			//enable dragging if clicked on textbox
			if ( !e.Handled && owner.SelectedTopic != Topic )
			{
				e.Handled = true;
				MouseButtonEventArgs args = new MouseButtonEventArgs( e.MouseDevice, e.Timestamp, e.ChangedButton );
				args.RoutedEvent = Mouse.MouseDownEvent;
				RaiseEvent( args );
			}
		}

		private static void OnNameTextBoxPreviewDrag( object sender, DragEventArgs e )
		{
			e.Handled = true;
		}

		private void OnChildrenToggleButtonClick( object sender, RoutedEventArgs e )
		{
			ToogleChildrenRecursive( this, AreChildrenVisible );

			owner.InvalidateTopicsArrange();
		}

		private static void ToogleChildrenRecursive( TopicContentPresenter presenter, bool visible )
		{
			foreach ( TopicContentPresenter child in presenter.Children )
			{
				if ( visible )
				{
					if ( presenter.AreChildrenVisible )
					{
						presenter.owner.AddChild( child );
					}
					else
					{
						continue;
					}
				}
				else
				{
					presenter.owner.RemoveChild( child );
				}

				ToogleChildrenRecursive( child, visible );
			}
		}

		protected override void OnPreviewDragEnter( DragEventArgs e )
		{
			if ( AdornerService != null )
			{
				dragAdorner = AdornerService.AdornTopic( Topic );
				e.Handled = true;
			}
			base.OnPreviewDragEnter( e );
		}

		protected override void OnPreviewDragLeave( DragEventArgs e )
		{
			if ( dragAdorner != null )
			{
				if ( AdornerService != null )
				{
					AdornerService.RemoveAdorner( dragAdorner );
					dragAdorner = null;
					e.Handled = true;
				}
			}
			base.OnPreviewDragLeave( e );
		}

		protected override void OnPreviewDrop( DragEventArgs e )
		{
			if ( dragAdorner != null )
			{
				if ( AdornerService != null )
				{
					AdornerService.RemoveAdorner( dragAdorner );
					dragAdorner = null;
					e.Handled = true;
				}
			}
			base.OnPreviewDrop( e );
		}

		protected override void OnPreviewMouseLeftButtonDown( MouseButtonEventArgs e )
		{
			if ( !e.Handled )
			{
				//if the click is on toggle children button then do nothing, because it should handle the event
				if ( childrenToggleButton != null && !childrenToggleButton.IsMouseOver )
				{
					if ( owner.SelectedTopic != Topic )
					{
						owner.SelectedTopic = Topic;
						//e.Handled = true;
					}

					if ( !IsFocused )
					{
						Focus();
					}
				}
			}

			base.OnPreviewMouseLeftButtonDown( e );
		}

		protected override void OnKeyDown( KeyEventArgs e )
		{
			base.OnKeyDown( e );

			if ( !e.Handled )
			{
				if ( e.Key == Key.Enter )
				{
					if ( !IsFocused ) //|| IsEditingTopicName )
					{
//                        IsEditingTopicName = false;
						e.Handled = true;
						Focus();
					}
				}
					//if there is a modifier key, probably this is a command and should not be handled
				else if ( e.KeyboardDevice.Modifiers != ModifierKeys.None )
				{
					return;
				}
				else if ( !nameTextBox.IsFocused )
				{
					nameTextBox.Focus();
					nameTextBox.SelectAll();
				}
			}
		}

//        protected override void OnMouseUp( MouseButtonEventArgs e )
//        {
//            base.OnMouseUp( e );
//
//            if ( !IsEditingTopicName )
//            {
//                IsEditingTopicName = true;
//                Focus();
//            }
//        }

		protected override void OnLostFocus( RoutedEventArgs e )
		{
			base.OnLostFocus( e );

			// Focus is not yet traversed to IsFocused of SubjectTextBox, so we will use Focus Manager
			// If the Focus is lost we should no long be in edit mode
			var focusScope = FocusManager.GetFocusScope( this );
			if ( FocusManager.GetFocusedElement( focusScope ) != nameTextBox )
			{
//                IsEditingTopicName = false;
				e.Handled = true;
			}
		}

		protected override Size MeasureOverride( Size constraint )
		{
			if ( Children.Count > 0 )
			{
				foreach ( TopicContentPresenter child in Children )
				{
					child.Measure( constraint );
				}
			}

			return base.MeasureOverride( constraint );
		}

		public void ExpandChildren()
		{
			if ( !AreChildrenVisible )
			{
				ToogleChildren();
			}
		}

		public void CollapseChildren()
		{
			if ( AreChildrenVisible )
			{
				ToogleChildren();
			}
		}

		private void ToogleChildren()
		{
			childrenToggleButton.IsChecked = !childrenToggleButton.IsChecked;
			RoutedEventArgs e = new RoutedEventArgs( ButtonBase.ClickEvent );
			childrenToggleButton.RaiseEvent( e );
		}
	}
}